/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
package openbackup.system.base.util;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import openbackup.system.base.common.exception.LegoCheckedException;
import openbackup.system.base.common.utils.MemoryCacheUtil;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.modules.junit4.PowerMockRunner;

import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * 功能描述
 *
 */
@RunWith(PowerMockRunner.class)
public class MemoryCacheUtilTest{

    private static final String CACHE_KEY = "test_key";
    private static final String LOADER_VALUE = "test_value";
    private static final Integer EXPIRE_TIME = 1000; // 1秒

    private Function<String, String> mockLoader;

    @Before
   public void setUp() {
        mockLoader = Mockito.mock(Function.class);
        Mockito.when(mockLoader.apply(CACHE_KEY)).thenReturn(LOADER_VALUE);
    }
    @After
    public void tearDown() throws Exception {
        Field cacheMapField = MemoryCacheUtil.class.getDeclaredField("cacheMap");
        cacheMapField.setAccessible(true);
        ((Map<String, MemoryCacheUtil.CacheEntry>) cacheMapField.get(null)).clear();
    }



    /**
     * 用例名称：验证直接从缓存中获取数据。
     * 前置条件：缓存中存在未过期数据。
     * 操作步骤：
     * 1. 首次调用getOrLoad，将数据加载到缓存。
     * 2. 第二次调用getOrLoad，直接从缓存获取数据。
     * 检查点：
     * - 返回值与预期值一致。
     * - loader仅被调用一次，验证未重复加载。
     */
    @Test
    public void test_getOrLoad_should_return_cached_value_when_cache_hit() {
        // 首次加载
        MemoryCacheUtil.getOrLoad(CACHE_KEY, mockLoader, EXPIRE_TIME);
        // 第二次调用，直接从缓存获取
        String result = MemoryCacheUtil.getOrLoad(CACHE_KEY, mockLoader, EXPIRE_TIME);
        assertEquals(LOADER_VALUE, result);
        Mockito.verify(mockLoader, Mockito.times(1)).apply(CACHE_KEY);
    }

    /**
     * 用例名称：验证缓存未命中时调用 loader 加载数据。
     * 前置条件：缓存中没有数据。
     * 操作步骤：
     * 1. 调用getOrLoad，由于缓存未命中，调用loader加载数据。
     * 检查点：
     * - 返回值与预期值一致。
     * - 缓存中存在新写入的数据。
     * - loader被调用一次。
     */
    @Test
    public void test_getOrLoad_should_call_loader_when_cache_miss() {
        // 当缓存不存在时，调用loader加载
        String result = MemoryCacheUtil.getOrLoad(CACHE_KEY, mockLoader, EXPIRE_TIME);
        assertEquals(LOADER_VALUE, result);
        Mockito.verify(mockLoader).apply(CACHE_KEY);
    }


    /**
     * 用例名称：验证并发访问时只加载一次数据。
     * 前置条件：多个线程同时访问getOrLoad方法。
     * 操作步骤：
     * 1. 创建多个线程同时调用getOrLoad。
     * 2. 确保所有线程都能正确获取数据。
     * 检查点：
     * - 所有线程返回正确的数据。
     * - loader仅被调用一次，验证并发控制有效。
     */
    @Test
    public void test_getOrLoad_should_load_once_when_concurrent_access() throws InterruptedException {
        // 创建多个线程同时调用getOrLoad
        int threadCount = 5;
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        for (int i = 0; i < threadCount; i++) {
            executor.submit(() -> {
                String result = MemoryCacheUtil.getOrLoad(CACHE_KEY, mockLoader, EXPIRE_TIME);
                assertEquals(LOADER_VALUE, result);
            });
        }
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        // 确保loader只被调用一次
        Mockito.verify(mockLoader, Mockito.times(1)).apply(CACHE_KEY);
    }

    /**
     * 用例名称：验证loader抛出异常时的处理。
     * 前置条件：loader方法抛出异常。
     * 操作步骤：
     * 1. 模拟loader抛出异常。
     * 2. 调用getOrLoad方法。
     * 检查点：
     * - 方法抛出LegoCheckedException。
     */
    @Test
    public void test_getOrLoad_should_throw_exception_when_loader_fails() {
        // 模拟loader抛出异常
        Function<String, String> errorLoader = key -> {
            throw new LegoCheckedException("Loader error");
        };
        // 调用getOrLoad，期望抛出LegoCheckedException
        assertThrows(LegoCheckedException.class, () -> {
            MemoryCacheUtil.getOrLoad(CACHE_KEY, errorLoader, EXPIRE_TIME);
        });
    }
}
